/**
 * 
 */
package it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.netgame;

import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.filehandlers.CBVMap;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.filehandlers.IniLoader;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.world.Map;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.world.MapManager;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.exceptions.InvalidFileException;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Massimo Canonaco
 *
 */
public class MainConnectionServer implements Runnable
{

	/** */
	static public final String NEW_ENTRY = "newEntry";
	
	/** */
	static public final String REMOVE_ENTRY = "remove";
	
	/** */
	static private final String SETTINGS_FILE = "cbvapps"+File.separator+"server.ini";
	
	/** */
	private Lock lock;

	/** */
	static public final int WELCOME_PORT = 6669;
	
	/** */
	static private final WaitingPlayerInformationBean NO_PLAYER = new WaitingPlayerInformationBean(-1, null);
	
	/** */
	private ConcurrentLinkedQueue<WaitingPlayerInformationBean> newPendingPlayers;
	
	/** */
	private int waitingKey;
	
	/** */
	private ServerSocket welcomeSocket;
	
	/** */
	private boolean listening; 
	
	/** */
	private ServerCondition serverCondition;
	
	/** */
	private String[] mapList;

	/** */
	private CBVMap mapBuilder;
	
	/** */
	private ConcurrentHashMap<WaitingPlayerInformationBean, WaitingPlayerInformationBean> waitingPlayers;
	
	/** */
	private String mapFolderPath;
	
	public MainConnectionServer() throws IOException
	{
		welcomeSocket = new ServerSocket(WELCOME_PORT);
		listening = false;
		waitingPlayers = new ConcurrentHashMap<WaitingPlayerInformationBean, WaitingPlayerInformationBean>();
		newPendingPlayers = new ConcurrentLinkedQueue<>();
		waitingKey = 0;
		lock = new ReentrantLock();
		serverCondition = new ServerCondition();
		
		HashMap<String, String> settings = new HashMap<>();
		IniLoader loader = new IniLoader(settings);
		loader.load(SETTINGS_FILE);
		File files = new File(settings.get("arenas.list.folderpath"));
		mapBuilder = new CBVMap();
		mapBuilder.getExtension();
		mapList = files.list(new FilenameFilter() {
			
			@Override
			public boolean accept(File file, String fileName) 
			{
				System.out.println(fileName+" "+file.isFile());
				return fileName.endsWith(mapBuilder.getExtension());
			}
		});
//		mapList = files.list();
		
		mapFolderPath = settings.get("arenas.list.folderpath");
		for(int index = 0; index < mapList.length; index++)
		{
			mapList[index] = mapList[index].replaceAll(mapBuilder.getExtension(), "");
			System.out.println(mapList[index]);
		}
//		mapBuilder.
	}
	
	@Override
	public void run()
	{
		listening = true;
		WaitingPlayerInformationBean temp = null;
		try 
		{
			new Thread(){
				
				@Override
				public void run() 
				{
					while(listening)
					{
						matching();
						try {
							sleep(35);
						} catch (InterruptedException e) 
						{
							e.printStackTrace();
						}
					}
				}}.start();
				
			while(listening)
			{
				temp = new WaitingPlayerInformationBean(waitingKey++, welcomeSocket.accept());
				new RequestMainServerConnectionHandler(this, temp, serverCondition).start();
			}
			
			System.out.println("SERVER CLOSED");
		} catch (IOException e) 
		{
			e.printStackTrace();
		} 
	}
	/**
	 * @return the listening
	 */
	public boolean isListening() 
	{
		return listening;
	}
	/**
	 * @param listening the listening to set
	 */
	public void setListening(boolean listening) 
	{
		this.listening = listening;
	}
	
	/** */
	public boolean noEnemy(final WaitingPlayerInformationBean key)
	{
		return waitingPlayers.get(key) == null || waitingPlayers.get(key) == NO_PLAYER;
	}
	
	/** */
	public WaitingPlayerInformationBean getEnemy(final WaitingPlayerInformationBean key)
	{
		return waitingPlayers.get(key);
	}
	
	/** */
	public void addNewEntry(WaitingPlayerInformationBean entry)
	{
		lock.lock();
			newPendingPlayers.add(entry);
		lock.unlock();
	}
	
	/** */
	public void matching()
	{
		if(!newPendingPlayers.isEmpty())
		{
			WaitingPlayerInformationBean temp = null;
			lock.lock();
				temp = newPendingPlayers.remove();
			lock.unlock();
			
			waitingPlayers.put(temp, NO_PLAYER);
		}
		
		if(waitingPlayers.size() % 2 == 0)
		{
			Enumeration<WaitingPlayerInformationBean> players = waitingPlayers.keys();
			WaitingPlayerInformationBean one = null, two = null;
			boolean signal = false, cicle = players.hasMoreElements();
			Random random = new Random();
			while(cicle)
			{
				one = getNextUnmatched(players);
				two = getNextUnmatched(players);
				if(one == null || two == null)
				{
					cicle = false;
				}
				else if(one != two && noEnemy(one) && noEnemy(two))
				{
					signal = true;
					one.setBeServer(true);
					two.setBeServer(false);
					waitingPlayers.put(one, two);
					waitingPlayers.put(two, one);
					
					String mapName = mapList[random.nextInt(mapList.length)];
					one.setArenaName(mapName);
					two.setArenaName(mapName);
					System.out.println("Su "+mapName+" : "+one+" VS "+two);
				}
			}
			if(signal)
			{
//				condition.signalAll();
				serverCondition.doNotifyAll();
			}
			
//			System.out.println("Server: pending players "+newPendingPlayers.size());
//			System.out.println("Server: added players "+waitingPlayers.size());
		}
	}

	/** */
	public boolean isValidMap(final String salt, final String name)
	{
		Map map = null;
		try 
		{
			map = mapBuilder.build(mapFolderPath, name);
		} catch (IOException | InvalidFileException e) 
		{
			return false;
		}

		return map.getSalt().equalsIgnoreCase(salt) && map.getName().equalsIgnoreCase(name);
	}
	
	/** */
	public void removePlayer(WaitingPlayerInformationBean bean)
	{
		synchronized (waitingPlayers) 
		{
			synchronized (newPendingPlayers) 
			{
				boolean removed = false;
				for (WaitingPlayerInformationBean player : newPendingPlayers)
				{
					if(player == bean)
					{
						removed = newPendingPlayers.remove(player);
						System.out.println("rimosso dalla entry coda: "+removed);
						break;
					}
				}

				if(!removed)
				{

					WaitingPlayerInformationBean player = waitingPlayers.get(bean);

					if(player != null)
					{
						waitingPlayers.remove(bean);
						waitingPlayers.put(player, NO_PLAYER);
						serverCondition.doNotifyAll();
					}

				}
			}
		}
	}
	
	/** */
	public void removePlayer(String keyValue)
	{
		final int KEY = Integer.parseInt(keyValue);
		synchronized (waitingPlayers) 
		{
			synchronized (newPendingPlayers) 
			{
				boolean removed = false;
				for (WaitingPlayerInformationBean player : newPendingPlayers)
				{
					if(player.getKey() == KEY)
					{
						removed = newPendingPlayers.remove(player);
						System.out.println("rimosso dalla entry coda: "+removed);
						break;
					}
				}

				if(!removed)
				{

					Enumeration<WaitingPlayerInformationBean> keys = waitingPlayers.keys();
					WaitingPlayerInformationBean tempBean = null;
					while(keys.hasMoreElements())
					{
						tempBean = keys.nextElement();
						
						if(tempBean.getKey() == KEY)
						{
							WaitingPlayerInformationBean enemy = waitingPlayers.get(tempBean);
							waitingPlayers.remove(tempBean);
							System.out.println("RIMOSSO "+KEY);
							if(enemy != NO_PLAYER)
							{
								waitingPlayers.put(enemy, NO_PLAYER);
							}
							serverCondition.doNotifyAll();
						}
					}
				}
			}
		}
	}
	
	/** */
	public boolean isIn(WaitingPlayerInformationBean bean)
	{
		boolean value = false;
		for (WaitingPlayerInformationBean pl : newPendingPlayers) 
		{
			if(pl == bean)
			{
				value = true;
				break;
			}
		}
		
		if(!value)
		{
			value = (waitingPlayers.get(bean) != null);
		}
		
		System.out.println("sei dentro caro amigo "+value);
		return value;
	}
	
	/** */
	private WaitingPlayerInformationBean getNextUnmatched(Enumeration<WaitingPlayerInformationBean> players)
	{
		WaitingPlayerInformationBean player = null, theReturn = null;
		boolean cicle = true;
		while(cicle && players.hasMoreElements())
		{
			player = players.nextElement();
			if(player != NO_PLAYER && (player.getConnectionSocket() == null || player.getConnectionSocket().isClosed()))
			{
//				removePlayer(player);
			}
			else if(noEnemy(player))
			{
				theReturn = player;
				cicle = false;
			}
		}
		return theReturn;
	}
	
	/**  */
	public static void main(String[] args) throws IOException 
	{
		MainConnectionServer server = new MainConnectionServer();
		new Thread(server).start();
		System.out.println("Server has been started!");
	}

	public File getMapFile(String arenaName) 
	{
		return new File(mapFolderPath+File.separator+arenaName);
	}
}
